"""
基于 authlib 实现 JWS（ JSON 网络签名 ）
替换 itsdangerous 1.1 以后被废弃的 TimedJSONWebSignatureSerializer （建议authlib等库实现）

相关issues:
https://github.com/pallets/itsdangerous/issues/129
https://github.com/pallets/itsdangerous/blob/fca4d63c1c3a443f824667405b8b7ff27c0c69d6/CHANGES.rst#id13
https://github.com/pallets/itsdangerous/blob/53f34c9a8fed28aa0f58502001ed2d75d23d3104/src/itsdangerous/jws.py

https://github.com/miguelgrinberg/Flask-HTTPAuth/issues/69
https://github.com/miguelgrinberg/Flask-HTTPAuth/pull/79/files
"""
import json
import time

from authlib.jose import JsonWebSignature, errors
from flask import current_app

from utils.crypto import Crypt


class TimedJSONWebSignatureSerializer(JsonWebSignature):
    """基于authlib 实现 支持过期时间的 JWS JSON 网络签名"""

    DEFAULT_EXPIRES_IN = 3600

    def __init__(self, secret_key, expires_in=None, protected=None, **kwargs):
        super().__init__(**kwargs)

        self.secret_key = secret_key

        if expires_in is None:
            expires_in = self.DEFAULT_EXPIRES_IN
        self.expires_in = expires_in

        if protected is None:
            # 该算法与itsdangerous.TimedJSONWebSignatureSerializer 默认算法相同
            # authlib支持算法 https://docs.authlib.org/en/latest/jose/jws.html#compact-serialize-and-deserialize
            protected = {"alg": "HS512"}
        self.protected = protected

    def dumps(self, confirm_info: dict):
        payload = json.dumps(
            {
                "confirm": Crypt.encrypt(json.dumps(confirm_info)),
                "exp": time.time() + self.expires_in,
            }
        )

        token = self.serialize_compact(self.protected, payload, self.secret_key)

        return token

    def loads(self, token: str):

        if not token:
            current_app.logger.exception("Token不存在")
            raise errors.InvalidTokenError("Token不存在")

        try:
            payload = self.deserialize_compact(
                token, self.secret_key, decode=json.loads
            )
        except errors.BadSignatureError as e:
            current_app.logger.exception("Token存在问题", exc_info=e)
            raise e
        except Exception as e:
            current_app.logger.exception("Token解析未知错误", exc_info=e)
            raise e

        decoded_payload = payload["payload"]
        exp = decoded_payload["exp"]

        if exp < time.time():
            raise errors.ExpiredTokenError("Token已过期")

        return json.loads(Crypt.decrypt(decoded_payload["confirm"]))
